<?php

/**
 * An easy-to-use reflection class for exposing an object's identity so
 * problems can be debugged quickly.
 * 
 * It does correct null/true/false/empty string detection.  A string just gets
 * output directly, whereas objects will have their class interfaces exposed. 
 * Everything dumped to screen is correctly escaped for HTML output so the rest
 * of the page doesn't break.
 * 
 * The following information is displayed for objects:
 * 
 * - Class file/line location (especially useful when the same name class is defined multiple times in the filesystem)
 * - Methods in the class, including parameter names.
 * - Contents of the object (simple print_r that is HTML escaped)
 * 
 * Example Usage:
 * 
 * <code>
 * SimpleReflector::jam($object); // echo info to screen
 * $str = SimpleReflector::jam($object, true); // returns info as a string
 * SimpleReflector::jam($object, false, 'crazy object'); // echo info to screen with a custom title instead of "SimpleReflector"
 * </code>
 * 
 * For quicker use, consider adding a short function to your applications:
 * 
 * <code>
 * function jam() { $args = func_get_args(); call_user_func_array(array('SimpleReflector', 'jam'), $args); }
 * </code>
 * 
 * Then instead of typing `SimpleReflector::jam(...)` you can just type `jam(...)`.
 * 
 * @package default
 * @author Anthony Bush
 * @copyright Academic Superstore 2006-2008, FreeBSD (revised) licensed
 * @version 1.0 (2008-10-20) - First public release after 2 years of internal-only use.
 * */
class SimpleReflector
{

    /**
     * Completely expose the contents of the given item in a way that makes it
     * easy to find out more about that item.
     * 
     * It displays invisible characters (nulls, boolean, empty strings) and
     * builds collapsable tables out of arrays and objects.
     *
     * @param mixed $var the object / variable to dump
     * @param boolean $return set to true if you want it to return the output rather than echo it (just like print_r)
     * @return mixed if $return is true, returns the output as string, otherwise it returns true.
     * @author Anthony Bush
     * */
    public static function jam( $var, $return = false, $overrideTitle = '' )
    {
        $html = '';
        if ( is_array( $var ) )
        {
            $html .= self::jamObject( $var, true, $overrideTitle );
        }
        else if ( is_object( $var ) )
        {
            $html .= self::jamObject( $var, true, $overrideTitle );
        }
        else
        {
            $html .= '<pre>';
            if ( strlen( $overrideTitle ) > 0 )
            {
                $html .= htmlentities( $overrideTitle );
            }
            else
            {
                $html .= 'SimpleReflector';
            }
            $html .= ': ' . self::getVisual( $var ) . '</pre>';
        }

        if ( $return )
        {
            return $html;
        }
        else
        {
            echo $html;
            return true;
        }
    }



    /**
     * Shows where item is defined (if it's an object) dumps it's contents, and
     * lists the public / private methods in a collapsable format.
     * 
     * @param mixed $var the object / variable to dump
     * @param boolean $return set to true if you want it to return the output rather than echo it (just like print_r)
     * @return mixed if $return is true, returns the output as string, otherwise it returns true.
     * @author Anthony Bush
     * */
    protected static function jamObject( $var, $return = false, $overrideTitle = '' )
    {
        $html = '';
        static $num  = 0;

        if ( is_object( $var ) )
        {

            $reflector = new ReflectionClass( $var );

            $html .= self::getShowHideJavascript();

            $html .= '<div class="debug">';
            $html .= '<a class="title" href="javascript:void(showHide(\'superjam' . $num . '\'))">';
            if ( strlen( $overrideTitle ) > 0 )
            {
                $html .= htmlentities( $overrideTitle );
            }
            else
            {
                $html .= 'SimpleReflector: ' . $reflector->getName();
            }
            $html .= '</a>';
            $html .= '<div id="superjam' . $num . '" class="superjam_results" style="display:none">';

            // Show where this class is defined:
            $html .= 'Definition: ' . $reflector->getFileName() . ':' . $reflector->getStartLine() . "<br />\n";

            // Get methods
            $methods = array(
                'public' => array( )
                , 'private' => array( )
                , 'protected' => array( )
            );
            foreach ( $reflector->getMethods() as $method )
            {
                if ( $method->isPrivate() )
                {
                    $access = 'private';
                }
                elseif ( $method->isProtected() )
                {
                    $access = 'protected';
                }
                else
                {
                    $access = 'public';
                }
                
                $methods[ $access ][ $method->getName() ] = $method;
            }
            foreach ( $methods as $access => $accessMethods )
            {
                ksort( $methods[ $access ] );
            }

            // Show methods
            ob_start();
            foreach ( $methods as $access => $accessMethods )
            {
                if ( !empty( $accessMethods ) )
                {
                    echo '<a class="' . $access . '" href="javascript:void(showHide(\'superjam_' . $access . $num . '\'))">Show/Hide ' . ucwords( $access ) . ' Methods</a>' . "<br />\n";
                    echo '<pre id="superjam_' . $access . $num . '" class="superjam_methods" style="display:none">';
                    foreach ( $accessMethods as $method )
                    {
                        $params = array( );
                        foreach ( $method->getParameters() as $param )
                        {
                            $params[ ]  = '<span class="methodParam">$' . $param->getName() . '</span>';
                        }
                        $paramNames = implode( ', ', $params );
                        printf(
                                '<div class="method ' . $access . '">'
                                . "%s%s%s "
                                . '<span class="methodName">'
                                . "%s</span>("
                                . $paramNames
                                . ');</div>'
                                , $method->isAbstract() ? ' abstract' : ''
                                , $method->isFinal() ? ' final' : ''
                                , $method->isStatic() ? ' static' : ''
                                , $method->getName()
                        );
                    }
                    echo '</pre>';
                }
            }
            $html .= ob_get_clean();


            // Show contents
            $html .= '<a class="superjam_contents" href="javascript:void(showHide(\'superjam_contents' . $num . '\'))">Show/Hide Contents</a>' . "<br />\n";
            $html .= '<pre id="superjam_contents' . $num . '" style="display:none">';
            $html .= htmlentities( print_r( $var, true ) );
            $html .= '</pre>';

            $html .= '</div>'; // superjam . $num
            $html .= '</div>'; // debug

            $num++;
        }
        else if ( is_array( $var ) )
        {

            // Just show contents
            $html .= self::getShowHideJavascript();
            $html .= '<div class="debug" style="border: 1px solid #000; background: #fff">';
            $html .= '<a class="title" style="display: block; background: #ddd; padding: 5px; font-weight: bold;" href="javascript:void(showHide(\'superjam' . $num . '\'))">';
            if ( strlen( $overrideTitle ) > 0 )
            {
                $html .= htmlentities( $overrideTitle );
            }
            else
            {
                $html .= 'SimpleReflector: PHP Array';
            }
            $html .= '</a>';
            $html .= '<pre id="superjam' . $num . '" style="display:none; padding: 5px">';
            $html .= htmlentities( print_r( $var, true ) );
            $html .= '</pre>';
            $html .= '</div>'; // debug

            $num++;
        }

        if ( $return )
        {
            return $html;
        }
        else
        {
            echo $html;
            return true;
        }
    }



    protected static function getAncestors( $class )
    {
        $classes = array( $class );
        while ( $class = get_parent_class( $class ) )
        {
            $classes[ ] = $class;
        }
        return $classes;
    }



    protected static function getVisual( $var )
    {
        if ( is_null( $var ) )
        {
            return '[null]';
        }
        else if ( $var === true )
        {
            return '[true]';
        }
        else if ( $var === false )
        {
            return '[false]';
        }
        else if ( $var === '' )
        {
            return '[empty string]';
        }
        else if ( is_array( $var ) )
        {
            return self::jamObject( $var, true );
        }
        else if ( is_object( $var ) )
        {
            return self::jamObject( $var, true );
        }
        else
        {
            return htmlentities( $var );
        }
    }



    /**
     * Internal function for printing out style / javascript that makes the
     * collapsing features work.
     * 
     * @return string
     * @author Anthony Bush
     * */
    protected static function getShowHideJavascript()
    {
        static $called = false;
        if ( $called )
        {
            return;
        }
        $called = true;
        ob_start();
        ?>
        <style type="text/css" media="screen">
            /* <![CDATA[ */
            .debug {
                border: 1px solid #000;
                background: #fff;
                color: #000;
            }
            .debug,
            .debug table td {
                text-align: left;
            }
            .debug table {
                margin: .5em 0;
            }
            .debug .title {
                display: block;
                background: #ddd;
                padding: 5px;
                font-weight: bold;
            }
            .debug a:link,
            .debug a:visited,
            .debug a:hover,
            .debug a:active {
                color: #0000A2;
                font-weight: bold;
            }
            .debug .superjam_methods {
                display: none;
            }
            .debug .superjam_results {
                display: none;
                padding: 5px;
            }
            .debug .methodName {
                color: #9D6F38;
            }
            /* ]]> */
        </style>
        <script type="text/javascript" language="javascript" charset="utf-8">
            // <![CDATA[
            function showHide(elementId) {
                e = document.getElementById(elementId);
                if (e.style.display == 'none') {
                    e.style.display = 'block';
                } else {
                    e.style.display = 'none';
                }
            }
            // ]]>
        </script>
        <?php

        return ob_get_clean();
    }



}
?>